<template>
  <div class="box">
    <div id="map"
         ref='map'>
    </div>
    <div class="distance"
         v-if="distance !== 0 ">
      总距离：{{ distance }} km
    </div>
    <button class="play"
            @click="animateFunc()">
      播放轨迹
    </button>
    <!-- <button class="play reset"
            @click="handleReset()">
      重置
    </button> -->
    <div ref='marker'
         class="marker"
         v-show='showDetail'
         @click.stop="showDetail = false">
      <span>车牌：粤ABCDE</span>
      <span>型号：本田</span>
      <span>颜色：白色</span>
      <span>剩余油量：200L</span>
      <span>距离终点距离：{{ lastKM }}km</span>
      <span>距离终点时间：18min</span>
    </div>
  </div>
</template>

<script lang='ts'>
import { Component, Watch, Vue } from 'vue-property-decorator'
// @ts-ignore
import mapboxgl from 'mapbox-gl'
import * as turf from '@turf/turf'

interface IGeoJSON {
  type: string
  features?: Array<IGeoJSON>
  properties?: any
  geometry?: IGeoJSON
  coordinates?: Array<any>
}

@Component({ name: 'Root' })
export default class App extends Vue {
  /**
   * ? state
   *  */
  private map: any = null // mapbox 对象
  private pointsGeojson: IGeoJSON = {
    type: 'FeatureCollection',
    features: [],
  } // 点数据
  private carGeojson: IGeoJSON = {
    type: 'FeatureCollection',
    features: [
      {
        type: 'Feature',
        geometry: {
          type: 'Point',
          coordinates: [],
        },
        properties: { bearing: null },
      },
    ],
  } // 汽车数据
  private showDetail: boolean = false
  private lastKM: number = 0
  private delCount: number = 1

  /**
   * ? computed
   *  */
  // 线数据
  get linestringGeojson(): IGeoJSON {
    const hasCoordinates = this.pointsGeojson.features!.length !== 0
    return {
      type: 'Feature',
      geometry: {
        type: 'LineString',
        coordinates: hasCoordinates
          ? this.pointsGeojson.features!.map((v: any) => v.geometry.coordinates)
          : [],
      },
    }
  }

  // 总距离
  get distance(): number {
    const hasCoordinates = this.pointsGeojson.features!.length !== 0
    return hasCoordinates ? turf.length(this.linestringGeojson as any) : 0
  }

  // 汽车角度
  get carBearing(): number {
    return this.carGeojson.features![0].properties.bearing
  }

  /**
   * ? watch 实时更新 style-source
   *  */
  @Watch('linestringGeojson', { deep: true })
  watchLinestringGeojsonChange(val: IGeoJSON) {
    this.map.getSource('lines-geojson').setData(val)
  }

  @Watch('pointsGeojson', { deep: true })
  watchPointsGeojsonChange(val: IGeoJSON) {
    this.map.getSource('points-geojson').setData(val)
    const hasFirst = val.features!.length !== 0
    // 刷新 car
    if (hasFirst) {
      const carGeojson = this.carGeojson
      carGeojson.features![0].geometry!.coordinates! =
        val.features![0].geometry!.coordinates!
      carGeojson.features![0].properties!.bearing = 0
      this.carGeojson = carGeojson
      this.lastKM = 0
      this.delCount = 1
    }
  }

  @Watch('carGeojson', { deep: true })
  watchCarGeojsonChange(val: IGeoJSON) {
    this.map.getSource('car-geojson').setData(val)
  }

  @Watch('carBearing')
  watchCarBearingChange(newVal: number, oldVal: number) {
    // console.log(newVal, oldVal)
    // Math.floor(oldVal).toFixed(2)
    const change: number = Math.abs(
      parseFloat(Math.floor(oldVal).toFixed(2)) -
        parseFloat(Math.floor(newVal).toFixed(2))
    )
    // if (aa !== 0) console.log(aa)
    if (oldVal && change > 0.1) this.delCount += 0.5
    // console.log(this.delCount)
  }

  /**
   * ? methods
   *  */

  /**
   * mapbox 初始化
   * ! 1. 显示地图
   *  */
  private mapInitFunc(): void {
    mapboxgl.accessToken =
      'pk.eyJ1Ijoic2FnaXQtemgiLCJhIjoiY2t0bno1cGp6MDdxNjJubGdzZHQxOTVtaiJ9.shmGZyFnHkC2rc5d5dHgBA'
    const map: any = new mapboxgl.Map({
      container: 'map',
      center: [113.3089, 23.0654],
      style: 'mapbox://styles/mapbox/streets-v11',
      hash: true,
      zoom: 9,
    })
    this.map = map
  }

  /* mapbox 配置 */
  private mapOnLoadFunc(): void {
    this.map.on('load', () => {
      /**
       * ! addSource
       *  */
      this.map.addSource('points-geojson', {
        type: 'geojson',
        data: this.pointsGeojson,
      })

      this.map.addSource('lines-geojson', {
        type: 'geojson',
        data: this.linestringGeojson,
      })

      this.map.addSource('car-geojson', {
        type: 'geojson',
        data: this.carGeojson,
      })

      /**
       * ! addLayer
       *  */

      this.map.addLayer({
        id: 'points',
        type: 'circle',
        source: 'points-geojson',
        paint: { 'circle-radius': 5, 'circle-color': '#000' },
        filter: ['in', '$type', 'Point'],
      })

      this.map.addLayer({
        id: 'lines',
        type: 'line',
        source: 'lines-geojson',
        layout: { 'line-cap': 'round', 'line-join': 'round' },
        paint: { 'line-color': '#000', 'line-width': 2.5 },
        filter: ['in', '$type', 'LineString'],
      })

      this.map.addLayer({
        id: 'car',
        source: 'car-geojson',
        type: 'symbol',
        layout: {
          'icon-image': 'car-15',
          'icon-size': 1.5,
          'icon-rotate': ['get', 'bearing'],
          'icon-rotation-alignment': 'map',
          'icon-allow-overlap': true,
          'icon-ignore-placement': true,
        },
      })

      this.mapOnClickFunc()
    })
  }

  /**
   * 点击事件
   * ! 2. 生成轨迹线路
   */
  private mapOnClickFunc(): void {
    this.map.on('click', (e: any) => {
      const monument: [number, number] = [e.lngLat.lng, e.lngLat.lat] // 点击坐标
      this.showDetail = false

      // 查找 car
      const carFeatures = this.map.queryRenderedFeatures(e.point, {
        layers: ['car'],
      })

      // 点击汽车
      if (carFeatures.length) {
        this.handleShowDetail(monument)
        /**
         * ! 计算剩余距离
         *  */
        const obj: IGeoJSON = JSON.parse(JSON.stringify(this.linestringGeojson))
        obj.geometry!.coordinates!.splice(0, this.delCount)
        obj.geometry!.coordinates!.unshift(monument)
        this.lastKM = turf.length(obj as any)

        this.showDetail = true
        return false
      }

      // 查找点
      const pointsFeatures = this.map.queryRenderedFeatures(e.point, {
        layers: ['points'],
      })

      // 删除点
      if (pointsFeatures.length) {
        const { id } = pointsFeatures[0].properties
        this.pointsGeojson.features! = this.pointsGeojson.features!.filter(
          (v: any) => v.properties.id !== id
        )
        return false
      }

      // 添加点
      const point = {
        type: 'Feature',
        geometry: {
          type: 'Point',
          coordinates: monument,
        },
        properties: { id: String(new Date().getTime()) },
      }
      this.pointsGeojson.features!.push(point)
    })
  }
  /**
   * ! 4. 播放轨迹
   *  */
  private animateFunc(): void {
    const step: number = 50 // 步长

    let counter: number = 0 // 下标

    // 用于存储路线坐标的数组
    const arc: Array<Array<number>> = []
    for (let i = 0; i < this.distance; i += this.distance / step) {
      const setpar = turf.along(this.linestringGeojson as any, i)
      arc.push(setpar.geometry.coordinates)
    }

    // 落点
    arc.push(
      this.pointsGeojson.features![this.pointsGeojson.features!.length - 1]
        .geometry!.coordinates!
    )
    // console.log(arc.length)

    const animate = () => {
      // 起始点
      const start: Array<number> = arc[counter]
      const end: Array<number> = arc[counter + 1]
      if (!end || !start) return false

      const carGeojson: IGeoJSON = this.carGeojson
      // 坐标
      carGeojson.features![0].geometry!.coordinates! = start
      // 角度
      const bearing: number = turf.bearing(turf.point(start), turf.point(end))
      carGeojson.features![0].properties.bearing = bearing

      // 更新
      this.carGeojson = carGeojson
      this.map.getSource('car-geojson').setData(carGeojson)

      // 动画
      if (counter < step) requestAnimationFrame(animate)

      counter++
    }
    animate()
  }
  /**
   * ! 5. 显示信息牌
   */
  private handleShowDetail(monument: any): void {
    const el: Vue | Element | Array<Vue | Element> | undefined =
      this.$refs.marker
    new mapboxgl.Marker(el).setLngLat(monument).addTo(this.map)
  }

  // /* 重置 */
  // private handleReset(): void {
  //   this.pointsGeojson = {
  //     type: 'FeatureCollection',
  //     features: [],
  //   }
  //   this.showDetail = false
  //   this.lastKM = 0
  //   this.delCount = 1
  // }

  mounted() {
    this.mapInitFunc()
    this.mapOnLoadFunc()
  }
}
</script>

<style scoped>
#map {
  margin: 0 atuo;
  width: 1000px;
  height: 500px;
}

.distance {
  position: fixed;
  top: 10px;
  left: 10px;
  z-index: 1;
  background-color: rgba(0, 0, 0, 0.5);
  color: #fff;
  font-size: 11px;
  line-height: 18px;
  display: block;
  margin: 0;
  padding: 5px 10px;
  border-radius: 3px;
}

.play {
  position: fixed;
  top: 50px;
  left: 10px;
  background-color: #3386c0;
  color: #fff;
  display: inline-block;
  margin: 0;
  padding: 10px 20px;
  border: none;
  cursor: pointer;
  border-radius: 3px;
}

/* .reset {
  top: 100px;
} */

.marker {
  /* background-image: url('https://docs.mapbox.com/mapbox-gl-js/assets/washington-monument.jpg'); */
  background-color: rgba(0, 0, 0, 0.5);
  /* background-size: cover; */
  /* width: 100px; */
  padding: 0 20px;
  height: 200px;
  border-radius: 5px;
  /* cursor: pointer; */
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: space-evenly;
  color: #fff;
}

.popup {
  max-width: 200px;
}
</style>